package ru.myrtle.jst.ant;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.DirSet;
import ru.myrtle.jst.Problem;
import ru.myrtle.jst.Project;
import ru.myrtle.jst.Syntax;
import ru.myrtle.jst.emitter.java.JavaEmitter;
import ru.myrtle.jst.store.Instance;
import ru.myrtle.jst.store.InstanceStore;
import ru.myrtle.jst.store.Template;
import ru.myrtle.jst.store.TemplateStore;

public class JstCompileTask extends Task {

    private static final String JAVA_EXTENSION = ".java";

    private DirSet sources;

    public void addConfiguredSources(DirSet sources) {
        if (this.sources != null) {
            throw new BuildException("Too many sources");
        }

        this.sources = sources;
    }

    /*
     *
     */

    private String sourceEncoding;

    public String getSourceEncoding() {
        return sourceEncoding;
    }

    public void setSourceEncoding(String sourceEncoding) {
        this.sourceEncoding = sourceEncoding;
    }

    /*
     *
     */

    private File destination;

    public void setDestination(File destination) {
        this.destination = destination;
    }

    /*
     *
     */

    private String destinationEncoding;

    public String getDestinationEncoding() {
        return destinationEncoding;
    }

    public void setDestinationEncoding(String destinationEncoding) {
        this.destinationEncoding = destinationEncoding;
    }

    /*
     *
     */

    @Override
    public void execute() throws BuildException {
        if (sources == null) {
            throw new BuildException("Sources not set.");
        }
        if (destination == null) {
            throw new BuildException("Destination directory not set.");
        }

        if (sources.isReference()) {
            sources = (DirSet) sources.getRefid().getReferencedObject();
        }

        sources.setDir(
                getProject().getBaseDir()
        );

        Project compiler = new Project();

        compiler.setTemplateStore(
                new TemplateStoreImpl()
        );
        compiler.setSyntax(
                new Syntax()
        );
        compiler.setEmitter(
                new JavaEmitter()
        );
        compiler.setInstanceStore(
                new InstanceStoreImpl()
        );

        try {
            compiler.compile();

            if (compiler.hasProblems()) {
                StringWriter writer = new StringWriter();

                PrintWriter printer = new PrintWriter(
                        writer
                );

                for (Problem problem : compiler.getProblems()) {
                    printer.println(
                            String.format(
                                    "%s | %s | %s | %s | %s",
                                    problem.getUnit().getTemplate().getName(),
                                    problem.getSeverity(),
                                    problem.getMessage(),
                                    problem.getLine(),
                                    problem.getColumn()
                            )
                    );
                }

                printer.flush();

                if (compiler.hasProblems(EnumSet.of(Problem.Severity.ERROR))) {
                    throw new BuildException(
                            writer.toString()
                    );
                }
                else {
                    System.out.println(
                            writer.toString()
                    );
                }
            }
        }
        catch (IOException thrown) {
            throw new BuildException(
                    thrown.getMessage(), thrown
            );
        }
    }

    /*
     *
     */

    class TemplateStoreImpl implements TemplateStore {

        List<Template> templates;

        @Override
        public Collection<Template> getTemplates() {
            if (templates == null) {
                templates = populate();
            }
            return templates;
        }

        List<Template> populate() {
            List<Template> templates = new ArrayList<Template>();

            DirectoryScanner directoryScanner = sources.getDirectoryScanner(
                    getProject()
            );

            for (String includedDirectory : directoryScanner.getIncludedDirectories()) {
                File directory = new File(
                        getProject().getBaseDir(), includedDirectory
                );

                for (File file : directory.listFiles()) {
                    if (!file.isFile()) {
                        continue;
                    }
                    if (!file.exists()) {
                        continue;
                    }

                    templates.add(
                            new TemplateImpl(file)
                    );
                }
            }

            return templates;
        }
    }

    /*
     *
     */

    class TemplateImpl implements Template {

        final File file;

        TemplateImpl(File file) {
            this.file = file;
        }

        @Override
        public String getName() {
            return file.getName();
        }

        @Override
        public Reader getContentReader() throws IOException {
            if (sourceEncoding == null) {
                return new FileReader(file);
            }
            else {
                return new InputStreamReader(
                        new FileInputStream(
                                file
                        ),
                        sourceEncoding
                );
            }
        }
    }

    /*
     *
     */

    class InstanceStoreImpl implements InstanceStore {

        static final String PACKAGE_SEPARATOR = "\\.";
        static final String PATH_SEPARATOR = "/";

        @Override
        public Instance createInstance(String namespace, String name) {
            File directory = new File(
                    destination, packageNameToPathName(namespace)
            );

            directory.mkdirs();

            File file = new File(
                    directory, name + JAVA_EXTENSION
            );

            return new InstanceImpl(file);
        }

        String packageNameToPathName(String packageName) {
            return packageNameToPathName(packageName, null);
        }

        String packageNameToPathName(String packageName, String pathSeparator) {
            if (pathSeparator == null) {
                pathSeparator = PATH_SEPARATOR;
            }

            return packageName.replaceAll(PACKAGE_SEPARATOR, pathSeparator);
        }
    }

    /*
     *
     */

    class InstanceImpl implements Instance {

        final File file;

        InstanceImpl(File file) {
            this.file = file;
        }

        @Override
        public Writer getContentWriter() throws IOException {
            if (destinationEncoding == null) {
                return new FileWriter(file);
            }
            else {
                return new OutputStreamWriter(
                        new FileOutputStream(
                                file
                        ),
                        destinationEncoding
                );
            }
        }
    }
}
